package collaboration.common.apps;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.SwingConstants;

import collaboration.client.CollabClient;
import collaboration.common.CollaborationApp;

/**
 * The <code>TicTacToe</code> extends <code>CollaborationApp</code> and contains 
 * all the methods necessary to setup a collaborative TicTacToe Game for two Players.
 * The host is the first player and one of their clients is the second player.
 * All other connected clients are observers.
 * 
 * @author Trevor Clack 6286191
 * @author Bowen Cheng 6204467
 * @version November 26, 2012 (1.42)
 */
public class TicTacToe extends CollaborationApp implements ActionListener {
	
	//------------------------
	// CLASS VARIABLES
	//------------------------

	/**
	 * The unique identifier for this application.
	 */
	public static final long serialVersionUID = 6343520547013896230L;

	/**
	 * The number of players allowed.
	 */
	final public static int NUM_PLAYERS = 2;

	//------------------------
	// MEMBER VARIABLES
	//------------------------
	
	private int[][] winCombinations = new int[][] {
			{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, //horizontal wins
			{0, 3, 6}, {1, 4, 7}, {2, 5, 8}, //vertical wins
			{0, 4, 8}, {2, 4, 6}			 //diagonal wins
	};
	private JButton buttons[] = new JButton[9];
	private String letters[] = {"O","X"};
	private String[] players = new String[NUM_PLAYERS];
	private String[] sentCommandString;
	private String letter, message, commandPrefix;
	private int count, ranCommands, index;
	private boolean win;

	/**
	 * The <code>TicTacToe</code> constructor.
	 * 
	 * @param aCollabClient The instance of <code>CollabClient</code> for this <code>TicTacToe</code>.
	 * @param player0 The host of the game, also player X.
	 * @param player1 The other player, also player O. 
	 * @param index The index for this instance of <code>TicTacToe</code>.
	 */
	public TicTacToe(CollabClient aCollabClient, String player0, String player1, int index) {
		super(aCollabClient, index);
		applicationName = "TicTacToe";
		players[0] = player0;
		players[1] = player1;
		commandPrefix = "^" + serialVersionUID + "#" + index;
		setLayout(new GridLayout(3,3));
		setPreferredSize(new Dimension(300,300));
		setMinimumSize(new Dimension(100, 100));
		setToolTipText(applicationName + " " + (index + 1));
		StringBuffer aBuffer = new StringBuffer("A new " + applicationName+ " game started");
		aBuffer.append(" at index: " + index);
		getCollabClient().addRecord(aBuffer);

		/*Add Buttons To panel*/
		for(int i = 0; i < 9; i++){
			String tooltip = i + ".";
			buttons[i] = new JButton();
			buttons[i].setVerticalAlignment(SwingConstants.CENTER);
			buttons[i].setHorizontalAlignment(SwingConstants.CENTER);
			buttons[i].setPreferredSize(new Dimension(30,30));
			buttons[i].setMinimumSize(new Dimension(10,10));
			add(buttons[i]);
			buttons[i].addActionListener(this);
			switch (i){
			case 0: case 1: case 2:
				tooltip += "Top ";
				break;
			case 3: case 4: case 5:
				tooltip += "Middle ";
				break;
			case 6: case 7: case 8:
				tooltip += "Bottom ";
				break;
			}
			switch (i % 3){
			case 0:
				tooltip += "Left";
				break;
			case 1:
				tooltip += "Middle";
				break;
			case 2:
				tooltip += "Right";
				break;
			}
			buttons[i].setToolTipText(tooltip);
		}
		resetBoard();
	}
	
	/**
	 * This method generates a <code>String</code> representation of the game board.
	 * 
	 * @return A <code>String</code> representation of the game board.
	 */
	public String toString(){
		StringBuffer board = new StringBuffer();
		for(int i = 0; i < 9; i++){
			if(buttons[i].getText().isEmpty()){
				board.append(" ");
			}else{
				int index = buttons[i].getText().indexOf("X");
				if(index == -1){
					index = buttons[i].getText().indexOf("O");
				}
				// Get only the letter, not html markup
				board.append(buttons[i].getText().substring(index, index+1));
			}
			switch (i){
			case 0: case 1: case 3: case 4:
			case 6: case 7:
				board.append("|");
				break;
			case 2: case 5:
				board.append("\n-----\n");
				break;
			}
		}
		return board.toString();
	}

	/**
	 * An overridden hook method called if there is application
	 * communication inbound.
	 * 
	 * @param msg The message to be received.
	 */
	@Override
	public void handleMessageFromAppUI(StringBuffer msg){
		String[] incomingCommandString;
		String message = msg.toString();
		if (message.startsWith(commandPrefix)) { // All application commands should start with commandPrefix
			incomingCommandString =  message.split(",");
			parseCommand(incomingCommandString);
		}// ignore if the command is not properly formatted or not for this application
	}

	/**
	   * An Overridden hook method called to return the applications
	   * serialVersionUID.
	   *
	   * @return The applications serialVersionUID.
	   */
	@Override
	public long getUniqueID(){
		return serialVersionUID;
	}
	
	/**
	 * An Overridden hook method called to return the applications
	 * name.
	 *
	 * @return The applications name.
	 */
	@Override
	public String getApplicationName(){
		return applicationName;
	}
	
	/**
	 * When an object is clicked, perform an action.
	 * This is the brains of the TicTacToe game.
	 * 
	 * @param a An action event object
	 */
	@Override
	public void actionPerformed(ActionEvent a) {
		String player = players[(count % 2)];
		message = "Move Player " + player + ",";
		count++;
		message += "Move number " + count + ",";

		/*Calculate whose turn it is*/
		letter = letters[count % 2];
		message += "Letter " + letter + " ";

		/*Write the letter to the button and deactivate it*/
		JButton pressedButton = (JButton)a.getSource();
		pressedButton.setText(
				"<html><div style='text-align:center;font-size:20px;'>" + 
				letter + "</div><div style='text-align:center;font-size:8px;'>(" + 
				player + ")</div></html>");
		pressedButton.setEnabled(false);
		int buttonNumber = Integer.parseInt(Character.toString(pressedButton.getToolTipText().charAt(0)));
		message += "placed at " + buttonNumber + ",";
		
		/*Show the board to the appropriate player*/
		if((count % 2) == 1 ){//Player0 (X) just played
			if(isHost()){//Deactivate board
				viewerBoard();
			}else if(isPlayer()){//Activate board
				playerBoard();
			}
		}else{//Player1 (O) just played
			if(isHost()){//Activate board
				playerBoard();
			}else if(isPlayer()){//Deactivate board
				viewerBoard();
			}
		}

		/*Determine who won*/
		checkWin();
		
		/* Send the message for the move*/
		if(isHost() || isPlayer()){
			StringBuffer msg= new StringBuffer();
			for(int index = 0; index < sentCommandString.length; index++){
				msg.append(sentCommandString[index] + ",");
			}
			msg.append(buttonNumber);
			sentCommandString = msg.toString().split(",");
			try {
				sendToServer(msg);
			} catch (IOException e) {
				//Error sending message do nothing
			}
			//Clean up
			msg = null;
		}
		
		message += getToolTipText() + " is won: " + win;
		message += CollabClient.NEWLINE + toString();
		
		if(debug){
			getCollabClient().getCollabClientUI().display(message);
		}

		/*Show a dialog when game is over*/
		if(isHost()){
			hostDialog();
		}else{
			othersDialog();
		}
		
		/*send the game result to server if anyone has won*/
		if(win)
		{
			StringBuffer gameResult = new StringBuffer();
			if((count % 2) == 1 )//Player0 (X) just played
			{
				gameResult.append(players[0]);
			}
			else
			{
				gameResult.append(players[1]);
			}
			gameResult.append(" has won " + applicationName +" at index " + index +"!");
			getCollabClient().addRecord(gameResult);
		}
	}
	
	/**
	 * The private class <code>WonOrTiedGameThread</code> extends <code>Thread</code> and 
	 * creates a message dialog box in a new Thread so that it does not interfere with any
	 * communications going on in the background. 
	 * 
	 * @author Trevor Clack 6286191
	 * @author Bowen Cheng 6204467
	 * @version November 26, 2012 (1.02)
	 */
	private class WonOrTiedGameThread extends Thread{
		private String message;
		private Component component;
		
		/**
		 * The Constructor for <code>WonOrTiedGameThread</code>.
		 * 
		 * @param comp The parent <code>Component</code> for this dialog.
		 * @param msg The message to display in this dialog.
		 */
		WonOrTiedGameThread(Component comp, String msg){
			component = comp;
			message = msg;
		}
		
		/**
		 * Runs the message dialog box.
		 */
		public void run() {
			JOptionPane.showMessageDialog(component, message);
			}
	}
	
	/**
	 * The private class <code>WonOrTiedHostGameThread</code> extends <code>Thread</code> and 
	 * creates a confirm dialog box in a new Thread so that it does not interfere with any
	 * communications going on in the background. 
	 * 
	 * @author Trevor Clack 6286191
	 * @author Bowen Cheng 6204467
	 * @version November 26, 2012 (1.02)
	 */
	class WonOrTiedHostGameThread extends Thread{
		private String message, title;
		private Component component;
		
		/** The Constructor for <code>WonOrTiedHostGameThread</code>.
		 * 
		 * @param comp The parent <code>Component</code> for this dialog.
		 * @param msg The message to display in this dialog.
		 * @param ttl The title for this dialog.
		 */
		WonOrTiedHostGameThread(Component comp, String msg, String ttl){
			component = comp;
			message = msg;
			title = ttl;
		}
		
		/**
		 * Launches the dialog and returns an <code>int</code> representing the button pressed.
		 * Returns:
		 * <code>JOptionPane.YES_OPTION</code> if the Yes button was pressed.
		 * <code>JOptionPane.NO_OPTION</code> if the No button was pressed.
		 * <code>JOptionPane.CANCEL_OPTION</code> if the dialog was closed.
		 * 
		 * @return The button selected by the host.
		 */
		public int launchDialogBox() {

			return JOptionPane.showConfirmDialog(component, message, title, JOptionPane.YES_NO_OPTION);

			}
	}
	
	/**
	 * Shows the proper dialog to the other player and spectators when the game is finished.
	 */
	private void othersDialog(){
		if(win){
			viewerBoard();
			WonOrTiedGameThread winner = new WonOrTiedGameThread(this,"<html><h1>" + letter + " wins game " + getToolTipText() + "!</h1>");
			winner.start();
			winner = null;//Clean up
			if(debug){
				message = "End " + getToolTipText();
				getCollabClient().getCollabClientUI().display(message);
			}
		}else if(count == 9 && win == false){
			viewerBoard();
			WonOrTiedGameThread tie= new WonOrTiedGameThread(this,"<html><h1>Game " + getToolTipText() + " was tie!</h1>");
			tie.start();
			tie = null;//Clean up
			if(debug){
				message = "End " + getToolTipText();
				getCollabClient().getCollabClientUI().display(message);
			}
		}
	}
	
	/**
	 * Shows the proper dialog to the host when the game is finished.
	 */
	private void hostDialog(){
		int option = -1000;// set to a number outside the range of JOptionPane
		if(win){
			viewerBoard();
			WonOrTiedHostGameThread winner = new WonOrTiedHostGameThread(this, "<html><h1>" + letter + " wins game " + getToolTipText() + "!</h1>" +
					"<br>Would You like another game?</html>",
					"Game finished");
			if(debug){
				message = "End " + getToolTipText();
				getCollabClient().getCollabClientUI().display(message);
			}
			option = winner.launchDialogBox();
		} else if(count == 9 && win == false){
			viewerBoard();
			WonOrTiedHostGameThread tie = new WonOrTiedHostGameThread(this, "<html><h1>Game " + getToolTipText() + " was tie!</h1>" +
					"<br>Would You like another game?</html>",
					"Game finished");
			if(debug){
				message = "End " + getToolTipText();
				getCollabClient().getCollabClientUI().display(message);
			}
			option = tie.launchDialogBox();
		}
		switch (option){
		case JOptionPane.YES_OPTION:
			resetBoard();
			break;
		case JOptionPane.NO_OPTION: case JOptionPane.CLOSED_OPTION:
			viewerBoard();
		default:
			break;
		}
	}
	
	/**
	 * Parses and runs the proper command based in the incoming command <code>String[]</code>.
	 * 
	 * @param incomingCommandString The incoming command <code>String[]</code>.
	 */
	private void parseCommand(String[] incomingCommandString){
		if(isHost() || isPlayer()){
			if(sentCommandString.length != incomingCommandString.length){// ignore repeats
				int doCommand = incomingCommandString.length;
				switch (doCommand){
				case 2:
					if (incomingCommandString[doCommand - 1].equals("close")){
						viewerBoard();
						StringBuffer msg = new StringBuffer(commandPrefix + ",close");
						sentCommandString = msg.toString().split(",");
						try {
							sendToServer(msg);
						} catch (IOException e) {
							//Error sending message do nothing
						}
						//Clean up
						msg = null;
						return;
					}
					break;
				case 4:
					if(incomingCommandString[doCommand - 3].equals("new")){
						resetBoard();
					}
					break;
				default:
					if(doCommand > 4){
						for(int command = doCommand - 1; command < incomingCommandString.length; command++){
							try {
								playerMove(Integer.parseInt(incomingCommandString[command]));
							}catch(Exception e){
								//Do nothing if error
							}
						}
					}
					break;
				}
				sentCommandString = incomingCommandString;
			}//sentCommandString.length == incomingCommandString.length do nothing
		}else{//Spectators, Reset and show all moves each time
			if(ranCommands!=incomingCommandString.length){// if duplicate command string ignore
				/*Hide the screen while we redraw it*/
				setVisible(false);
				
				/*Reset the Variables*/
				resetVars();

				/*Reset the Buttons*/
				for(int i = 0; i < 9; i++){
					buttons[i].setText("");
					buttons[i].setEnabled(false);
				}

				/*Re do all the moves*/
				for(int move = 4; move < incomingCommandString.length; move++){
					try {
						playerMove(Integer.parseInt(incomingCommandString[move]));
					}catch(Exception e){
						//Do nothing if error
					}
				}
				/*Finished! Show the screen*/
				setVisible(true);
				
				ranCommands = incomingCommandString.length;
			}
		}
	}
	
	/**
	 * Checks for a win and sets the <code>boolean won</code> appropriately.
	 * Sets  <code>won</code> to treu if the game is won and false otherwise.
	 */
	private void checkWin(){
		for(int i=0; i < 8; i++){
			if (buttons[winCombinations[i][0]].getText().equals(buttons[winCombinations[i][1]].getText()) && 
				buttons[winCombinations[i][1]].getText().equals(buttons[winCombinations[i][2]].getText()) && 
				buttons[winCombinations[i][0]].getText() != "")
			{
				String winner = "<html><body style='background-color:#006600'>" +
						(buttons[winCombinations[i][0]].getText().substring(
								buttons[winCombinations[i][0]].getText().indexOf(">")+1,
								buttons[winCombinations[i][0]].getText().lastIndexOf("</html>"))) + "</body></html>";
				buttons[winCombinations[i][0]].setText(winner);
				buttons[winCombinations[i][1]].setText(winner);
				buttons[winCombinations[i][2]].setText(winner);
				win = true;
			}
		}
	}
	
	/**
	 * Simulate a button press.
	 * 
	 * @param buttonNum The index of the button to press.
	 */
	private void playerMove(int buttonNum){
		if(buttonNum >= 0 && buttonNum < 9){
			buttons[buttonNum].setEnabled(true);
			buttons[buttonNum].doClick();
		}
	}
	
	/**
	 * Resets the game board.
	 */
	private void resetBoard(){
		/*Reset the Variables*/
		resetVars();

		/*Reset the Buttons*/
		for(int i = 0; i < 9; i++){
			buttons[i].setText("");
			buttons[i].setEnabled(true);
		}
		if(isHost() || isPlayer()){
			StringBuffer msg = new StringBuffer(commandPrefix + "," + 
					"new," + players[0]  + "," + players[1]);
			sentCommandString = msg.toString().split(",");
			try {
				sendToServer(msg);
			} catch (IOException e) {
				//Error sending message do nothing
			}
			//Clean Up
			msg = null;
		}
		if(isHost()){
			playerBoard();
		}else{
			viewerBoard();
		}
	}
	
	/**
	 * Is the current player the host?
	 * Returns true if it is the host and false otherwise.
	 * 
	 * @return A boolean that is true if it is the host and false otherwise.
	 */
	private boolean isHost(){
		return getCollabClient().getLoginID().equals(players[0]);
	}
	
	/**
	 * Is the current player the other player?
	 * Returns true if it is the other player and false otherwise.
	 * 
	 * @return A boolean that is true if it is the other player and false otherwise.
	 */
	private boolean isPlayer(){
		return getCollabClient().getLoginID().equals(players[1]);
	}
	
	/**
	 * Sets all the buttons to disabled.
	 */
	private void viewerBoard(){
		/*Reset the Buttons*/
		for(int i = 0; i < 9; i++){
			buttons[i].setEnabled(false);
		}
	}
	
	/**
	 * Sets the appropriate buttons to enabled.
	 */
	private void playerBoard(){
		/*Reset the Buttons*/
		for(int i = 0; i < 9; i++){
			if(buttons[i].getText().equals("")){
				buttons[i].setEnabled(true);
			}
		}
	}
	
	/**
	 * Resets all the game variables.
	 */
	private void resetVars(){
		count = 0;
		letter = "";
		win = false;
		if(debug){
			message = "New " + getToolTipText();
			getCollabClient().getCollabClientUI().display(message);
		}
	}
	
	/* Code from Umple - edited*/
	
	//------------------------
	// INTERFACE
	//------------------------

	/* (non-Javadoc)
	 * @see collaboration.common.CollaborationApp#delete()
	 */
	public void delete()
	  {
	    super.delete();
	    for (int numButton = 0; numButton < buttons.length; numButton++){
	    	buttons[numButton]=null;
	    }
	    buttons = null;
	    for(int numPlayer = 0; numPlayer < players.length; numPlayer++){
	    	players[numPlayer]=null;
	    }
	    players = null;
	  }
}
